package com.srmt.common.ratelimit.impl;

import com.srmt.common.ratelimit.RateLimiter;
import com.srmt.common.ratelimit.config.ApiEndpoint;
import com.srmt.common.ratelimit.config.RateLimitConfig;
import com.srmt.common.ratelimit.exception.RateLimitException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 本地限流器实现
 * 使用令牌桶算法，支持突发流量和重试队列
 * 
 * @author SRMT Development Team
 * @since 1.0.0
 */
public class LocalRateLimiter implements RateLimiter {
    
    private static final Logger logger = LoggerFactory.getLogger(LocalRateLimiter.class);
    
    /**
     * 令牌桶存储
     */
    private final Map<String, TokenBucket> buckets = new ConcurrentHashMap<>();
    
    /**
     * 重试请求队列
     */
    private final DelayQueue<RetryRequest> retryQueue = new DelayQueue<>();
    
    /**
     * 配置
     */
    private final RateLimitConfig config;
    
    /**
     * 调度器，用于定期补充令牌
     */
    private final ScheduledExecutorService scheduler;
    
    /**
     * 重试处理线程
     */
    private final Thread retryProcessor;
    
    /**
     * 是否已关闭
     */
    private volatile boolean shutdown = false;
    
    /**
     * 构造函数
     */
    public LocalRateLimiter(RateLimitConfig config) {
        this.config = config;
        this.scheduler = Executors.newScheduledThreadPool(1, r -> {
            Thread t = new Thread(r, "RateLimiter-TokenRefill");
            t.setDaemon(true);
            return t;
        });
        
        // 启动令牌补充任务
        scheduler.scheduleAtFixedRate(this::refillTokens, 0, 100, TimeUnit.MILLISECONDS);
        
        // 启动重试处理线程
        this.retryProcessor = new Thread(this::processRetryQueue, "RateLimiter-RetryProcessor");
        retryProcessor.setDaemon(true);
        retryProcessor.start();
        
        logger.info("本地限流器已初始化,默认QPS: {}", config.getDefaultQps());
    }
    
    @Override
    public boolean tryAcquire(String api, long timeout) {
        // 检查是否已经关闭
        if (shutdown) {
            logger.debug("[限流] API: {} - 限流器已关闭,拒绝请求", api);
            return false;
        }
        
        // 检查API名称是否为null
        if (api == null) {
            logger.debug("[限流] API名称为null,拒绝请求");
            return false;
        }
        
        TokenBucket bucket = getBucket(api);
        
        // 记录当前状态用于调试
        double tokensBefore = bucket.getTokensForDebug();
        int qps = bucket.getQps();
        logger.debug("[限流] API: {}, QPS: {}, Available tokens before acquire: {:.2f}", 
                    api, qps, tokensBefore);
        
        // 如果QPS为0，拒绝所有请求
        if (qps == 0) {
            logger.debug("[限流] API: {} - QPS为0,拒绝请求", api);
            return false;
        }
        
        // 第一次尝试立即获取令牌
        if (bucket.tryAcquire()) {
            logger.debug("[限流] API: {} - 立即获取令牌,剩余: {:.2f}",
                        api, bucket.getTokensForDebug());
            return true;
        }
        
        // 如果不需要等待，直接返回false
        if (timeout <= 0) {
            logger.warn("[限流] API: {} - 无可用令牌(当前{:.2f}),未指定超时时间,拒绝请求",
                       api, tokensBefore);
            return false;
        }
        
        // 如果有超时时间，进行阻塞等待
        long startTime = System.currentTimeMillis();
        long remainingTimeout = timeout;
        
        while (remainingTimeout > 0) {
            // 计算下次令牌可用时间
            long waitTime = bucket.getWaitTime();
            
            if (waitTime == 0) {
                // 令牌已经可用，立即尝试获取
                if (bucket.tryAcquire()) {
                    long totalWaitTime = System.currentTimeMillis() - startTime;
                    logger.info("[限流] API: {} - 等待{}ms后获取令牌,剩余: {:.2f}",
                               api, totalWaitTime, bucket.getTokensForDebug());
                    return true;
                }
            }
            
            // 计算实际等待时间（不超过剩余超时时间）
            long actualWaitTime = Math.min(waitTime, remainingTimeout);
            
            if (actualWaitTime <= 0) {
                break;
            }
            
            logger.trace("[限流] API: {} - 等待{}ms以获取下一个令牌(剩余超时: {}ms)",
                        api, actualWaitTime, remainingTimeout);
            
            try {
                // 等待一段时间
                Thread.sleep(Math.min(actualWaitTime, 50)); // 最多等待50ms，然后重新检查
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                logger.warn("[限流] API: {} - 等待令牌时被中断", api);
                return false;
            }
            
            // 更新剩余超时时间
            remainingTimeout = timeout - (System.currentTimeMillis() - startTime);
        }
        
        // 最后尝试一次
        if (bucket.tryAcquire()) {
            long totalWaitTime = System.currentTimeMillis() - startTime;
            logger.info("[限流] API: {} - 等待{}ms后获取令牌(最后尝试),剩余: {:.2f}",
                       api, totalWaitTime, bucket.getTokensForDebug());
            return true;
        }
        
        // 超时，获取失败
        logger.error("[限流] API: {} - {}ms超时后获取令牌失败,可用令牌: {:.2f}, QPS: {}",
                    api, timeout, bucket.getTokensForDebug(), qps);
        return false;
    }
    
    @Override
    public void acquire(String api) throws InterruptedException {
        if (api == null) {
            throw new IllegalArgumentException("API名称不能为null");
        }
        TokenBucket bucket = getBucket(api);
        long startTime = System.currentTimeMillis();
        
        while (!bucket.tryAcquire()) {
            // 计算等待时间
            long waitTime = bucket.getWaitTime();
            if (waitTime > 0) {
                Thread.sleep(Math.min(waitTime, 100));
            }
            
            // 检查是否已关闭
            if (shutdown) {
                throw new InterruptedException("限流器正在关闭");
            }
        }
        
        long elapsed = System.currentTimeMillis() - startTime;
        if (elapsed > 100) {
            logger.debug("为API [{}] 获取令牌,等待 {} ms", api, elapsed);
        }
    }
    
    @Override
    public void setQps(String api, int qps) {
        if (api == null) {
            throw new IllegalArgumentException("API名称不能为null");
        }
        if (qps < 0) {
            throw new IllegalArgumentException("QPS必须为非负数");
        }
        
        TokenBucket bucket = getBucket(api);
        bucket.setQps(qps);
        logger.info("设置API [{}] 的QPS为 {}", api, qps);
    }
    
    @Override
    public int getQps(String api) {
        if (api == null) {
            return config.getDefaultQps();
        }
        return getBucket(api).getQps();
    }
    
    @Override
    public int getAvailablePermits(String api) {
        if (api == null) {
            return 0;
        }
        return getBucket(api).getAvailableTokens();
    }
    
    @Override
    public void reset(String api) {
        if (api == null) {
            buckets.clear();
            logger.info("重置所有限流器");
        } else {
            buckets.remove(api);
            logger.info("重置API [{}] 的限流器", api);
        }
    }
    
    @Override
    public void shutdown() {
        shutdown = true;
        scheduler.shutdown();
        retryProcessor.interrupt();
        
        try {
            if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
                scheduler.shutdownNow();
            }
        } catch (InterruptedException e) {
            scheduler.shutdownNow();
            Thread.currentThread().interrupt();
        }
        
        logger.info("本地限流器关闭完成");
    }
    
    /**
     * 获取或创建令牌桶
     */
    private TokenBucket getBucket(String api) {
        return buckets.computeIfAbsent(api, k -> {
            // 优先使用配置的QPS值
            int qps = config.getApiQps(api);
            
            // 如果没有配置特定的API QPS，且存在预定义端点，使用端点的默认QPS
            // 注意：只有当API没有显式配置时才使用端点默认值
            RateLimitConfig.ApiConfig apiConfig = config.getApiConfigs().get(api);
            if (apiConfig == null || apiConfig.getQps() == 0) {
                ApiEndpoint endpoint = ApiEndpoint.fromCode(api);
                if (endpoint != null) {
                    qps = endpoint.getDefaultQps();
                    logger.debug("[限流] 为API: {}使用端点默认QPS: {}", api, qps);
                }
            }
            
            return new TokenBucket(api, qps);
        });
    }
    
    /**
     * 定期补充令牌
     */
    private void refillTokens() {
        if (logger.isTraceEnabled() && !buckets.isEmpty()) {
            logger.trace("[限流] 为{}个令牌桶定期补充令牌", buckets.size());
        }
        for (TokenBucket bucket : buckets.values()) {
            bucket.refill();
        }
    }
    
    /**
     * 处理重试队列
     */
    private void processRetryQueue() {
        while (!shutdown) {
            try {
                RetryRequest retry = retryQueue.take();
                if (retry.bucket.tryAcquire()) {
                    retry.complete(true);
                } else {
                    // 如果仍然无法获取，重新计算等待时间并重新入队
                    if (!retry.isExpired()) {
                        long newWaitTime = retry.bucket.getWaitTime();
                        retry.updateDelay(newWaitTime);
                        retryQueue.offer(retry);
                    } else {
                        retry.complete(false);
                    }
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
    
    /**
     * 令牌桶实现
     * 使用单一锁保护所有状态，确保线程安全
     */
    private static class TokenBucket {
        private final String api;
        private final ReentrantLock lock = new ReentrantLock();
        // 不再使用Atomic变量，改用普通变量+锁保护
        private int qps;
        private double tokens; // 使用double存储令牌，支持小数
        private long lastRefillTime;
        
        TokenBucket(String api, int qps) {
            this.api = api;
            this.qps = qps;
            // 初始令牌数：使用100%容量，符合标准令牌桶算法
            // 允许系统启动时的突发请求，提供更好的用户体验
            this.tokens = (double) qps;
            this.lastRefillTime = System.currentTimeMillis();
            logger.info("[限流] 为API: {}创建令牌桶, QPS: {}, 初始令牌数: {:.2f}",
                       api, qps, tokens);
        }
        
        /**
         * 尝试获取令牌
         */
        boolean tryAcquire() {
            lock.lock();
            try {
                // QPS为0时，不允许任何请求
                if (qps == 0) {
                    logger.trace("[限流] API: {} - QPS为0,拒绝请求", api);
                    return false;
                }
                
                double tokensBefore = tokens;
                // 先尝试补充令牌
                refillTokensLocked();
                double tokensAfterRefill = tokens;
                
                if (Math.abs(tokensBefore - tokensAfterRefill) > 0.001) {
                    logger.trace("[限流] API: {} - 补充令牌: {:.4f} -> {:.4f}",
                                api, tokensBefore, tokensAfterRefill);
                }
                
                // 需要至少1个完整的令牌才能通过
                if (tokens >= 1.0) {
                    tokens -= 1.0;
                    logger.debug("[限流] API: {} - 令牌已消费,剩余: {:.4f}", api, tokens);
                    return true;
                }
                
                // 令牌不足，记录详细信息帮助调试
                logger.debug("[限流] API: {} - 令牌不足: {:.4f} < 1.0, QPS: {}",
                            api, tokens, qps);
                return false;
            } finally {
                lock.unlock();
            }
        }
        
        /**
         * 补充令牌（必须在锁内调用）
         */
        private void refillTokensLocked() {
            // 如果QPS为0，不补充令牌
            if (qps == 0) {
                return;
            }
            
            long now = System.currentTimeMillis();
            long elapsed = now - lastRefillTime;
            
            // 只有经过足够时间才补充令牌
            // 至少要经过1ms，避免频繁的微小补充
            if (elapsed < 1) {
                return;
            }
            
            // 使用高精度计算
            // 每毫秒生成的令牌数
            double tokensPerMs = qps / 1000.0;
            
            // 计算应该补充的令牌数量
            double tokensToAdd = tokensPerMs * elapsed;
            
            // 只有补充量大于0.001才执行补充（避免浮点数误差）
            if (tokensToAdd > 0.001) {
                double oldTokens = tokens;
                // 添加令牌，但不超过桶容量
                // 桶容量设置为QPS值，允许一定的突发
                double maxTokens = Math.max(qps, 1.0);
                tokens = Math.min(tokens + tokensToAdd, maxTokens);
                // 更新最后补充时间为当前时间
                lastRefillTime = now;
                
                if (logger.isTraceEnabled() && Math.abs(oldTokens - tokens) > 0.001) {
                    logger.trace("[限流] API: {} - 补充{:.4f}个令牌(耗时: {}ms), 总计: {:.4f}/{:.0f}",
                                api, tokensToAdd, elapsed, tokens, maxTokens);
                }
            }
        }
        
        /**
         * 补充令牌（外部调用）
         */
        void refill() {
            lock.lock();
            try {
                refillTokensLocked();
            } finally {
                lock.unlock();
            }
        }
        
        /**
         * 获取下次令牌可用的等待时间
         */
        long getWaitTime() {
            lock.lock();
            try {
                // 先补充令牌，获取最新状态
                refillTokensLocked();
                
                // 如果已经有令牌，不需要等待
                if (tokens >= 1.0) {
                    return 0;
                }
                
                // 计算需要多少时间才能获得1个令牌
                // 需要的令牌数 = 1.0 - 当前令牌数
                double tokensNeeded = 1.0 - tokens;
                
                // 每毫秒生成的令牌数
                double tokensPerMs = qps / 1000.0;
                
                // 计算需要等待的时间
                if (tokensPerMs > 0) {
                    long waitTime = (long) Math.ceil(tokensNeeded / tokensPerMs);
                    logger.trace("[限流] API: {} - 需要{:.4f}个令牌, 等待时间: {}ms",
                                api, tokensNeeded, waitTime);
                    return waitTime;
                }
                
                // QPS为0，返回最大等待时间
                return 60000; // 60秒
            } finally {
                lock.unlock();
            }
        }
        
        int getQps() {
            lock.lock();
            try {
                return qps;
            } finally {
                lock.unlock();
            }
        }
        
        void setQps(int newQps) {
            lock.lock();
            try {
                int oldQps = qps;
                qps = newQps;
                
                // 如果新QPS为0，清空令牌
                if (newQps == 0) {
                    tokens = 0;
                }
                // 如果旧QPS为0，设置完整的初始令牌
                else if (oldQps == 0) {
                    tokens = (double) newQps;
                }
                // 按比例调整当前令牌数
                else {
                    tokens = (tokens * newQps) / (double)oldQps;
                    tokens = Math.min(tokens, newQps);
                }
                
                logger.debug("[限流] API: {} - QPS changed from {} to {}, tokens: {:.4f}", 
                            api, oldQps, newQps, tokens);
            } finally {
                lock.unlock();
            }
        }
        
        int getAvailableTokens() {
            lock.lock();
            try {
                // 先补充令牌以获取最新状态
                refillTokensLocked();
                // 返回可以使用的完整令牌数（向下取整）
                return Math.max(0, (int) Math.floor(tokens));
            } finally {
                lock.unlock();
            }
        }
        
        // 新增调试方法，获取精确的令牌数
        double getTokensForDebug() {
            lock.lock();
            try {
                return tokens;
            } finally {
                lock.unlock();
            }
        }
    }
    
    /**
     * 重试请求
     */
    private static class RetryRequest implements Delayed {
        private final String api;
        private final TokenBucket bucket;
        private final long createTime;
        private final long maxWaitTime;
        private final CountDownLatch latch = new CountDownLatch(1);
        private volatile long executeTime;
        private volatile boolean success = false;
        
        RetryRequest(String api, TokenBucket bucket, long delay) {
            this.api = api;
            this.bucket = bucket;
            this.createTime = System.currentTimeMillis();
            this.maxWaitTime = 30000; // 最多等待30秒
            this.executeTime = createTime + delay;
        }
        
        void updateDelay(long newDelay) {
            this.executeTime = System.currentTimeMillis() + newDelay;
        }
        
        boolean await(long timeout) throws InterruptedException {
            if (latch.await(timeout, TimeUnit.MILLISECONDS)) {
                return success;
            }
            return false;
        }
        
        void complete(boolean success) {
            this.success = success;
            latch.countDown();
        }
        
        boolean isExpired() {
            return System.currentTimeMillis() - createTime > maxWaitTime;
        }
        
        @Override
        public long getDelay(TimeUnit unit) {
            long delay = executeTime - System.currentTimeMillis();
            return unit.convert(delay, TimeUnit.MILLISECONDS);
        }
        
        @Override
        public int compareTo(Delayed o) {
            if (o instanceof RetryRequest) {
                RetryRequest other = (RetryRequest) o;
                return Long.compare(this.executeTime, other.executeTime);
            }
            return Long.compare(getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS));
        }
    }
}